﻿Imports ProtoBuf
Imports System.Runtime.Serialization
Imports System.Text

<DataContract, ProtoContract>
Public Class CSchemaInfo
	'High-level
	<DataMember(Order:=11)> Public Tables As CTableInfoList
	<DataMember(Order:=12)> Public Views As CViewInfoList
	<DataMember(Order:=13)> Public AllColumns As CColumnList
	<DataMember(Order:=14)> Public Procs As CProcedureList
	<DataMember(Order:=15)> Public PrimaryKeys As CPrimaryKeyList
	<DataMember(Order:=17)> Public ForeignKeys As CForeignKeyList
	<DataMember(Order:=18)> Public Indexes As CIndexInfoList
	<DataMember(Order:=19)> Public DefaultValues As CDefaultValueList


	'Data
	<DataMember(Order:=21)> Public Migration As CMigration
	<DataMember(Order:=22)> Public MigrationHistory As CMigrationHistory

	'Preconstructor
	Shared Sub New()
		CProto.Prepare(Of CSchemaInfo)()
	End Sub

	'Constructors
	Public Sub New(db As CDataSrc, Optional fullMigrationHistory As Boolean = False, Optional sysTables As Boolean = False)

		'Processed
		Me.Tables = New CTableInfoList(db, False)
		Me.Views = New CViewInfoList(db)
		Me.Procs = New CProcedureList(db)
		Me.DefaultValues = New CDefaultValueList(db)

		Me.PrimaryKeys = Me.Tables.PrimaryKeys
		Me.Indexes = Me.Tables.Indexes
		Me.ForeignKeys = Me.Tables.ForeignKeys
		Me.AllColumns = New CColumnList(Me.Tables.AllColumns)
		Me.AllColumns.AddRange(Me.Views.AllColumns)


		'Migrations
		Me.Migration = New CMigration
		Try
			If Me.Tables.Has("dbo.__MigrationHistory") Then Me.Migration = db.LatestMigration
		Catch
		End Try

		Me.MigrationHistory = New CMigrationHistory()
		Try
			If fullMigrationHistory Then
				If Me.Tables.Has("dbo.__MigrationHistory") Then Me.MigrationHistory = db.MigrationHistory
			End If
		Catch
		End Try
	End Sub
	Friend Sub New()
		Me.Tables = New CTableInfoList
		Me.Views = New CViewInfoList
		Me.Procs = New CProcedureList
		Me.Indexes = New CIndexInfoList
		Me.PrimaryKeys = New CPrimaryKeyList
		Me.ForeignKeys = New CForeignKeyList
		Me.DefaultValues = New CDefaultValueList()
		Me.Migration = New CMigration
	End Sub

	Public Function Diff(ref As CSchemaInfo) As CSchemaDiff
		Dim d As New CSchemaDiff
		Diff(Me.PrimaryKeys, ref.PrimaryKeys, d.Missing.PrimaryKeys, d.Extra.PrimaryKeys, d.Same.PrimaryKeys, d.Diff.PrimaryKeys)
		Diff(Me.Tables, ref.Tables, d.Missing.Tables, d.Extra.Tables, d.Same.Tables, d.Diff.Tables)
		Diff(Me.Views, ref.Views, d.Missing.Views, d.Extra.Views, d.Same.Views, d.Diff.Views)
		Diff(Me.Procs, ref.Procs, d.Missing.Procs, d.Extra.Procs, d.Same.Procs, d.Diff.Procs)
		Diff(Me.DefaultValues, ref.DefaultValues, d.Missing.DefaultValues, d.Extra.DefaultValues, d.Same.DefaultValues, d.Diff.DefaultValues)

		d.IndexDiff = Me.Indexes.Diff(ref.Indexes)
		d.ProcDiff = Me.Procs.Diff(ref.Procs)
		d.FKDiff = Me.ForeignKeys.Diff(ref.ForeignKeys)
		d.ViewDiff = Me.Views.Diff(ref.Views)
		d.TableDiff = Me.Tables.Diff(ref.Tables)
		d.DefaultDiff = Me.DefaultValues.Diff(ref.DefaultValues)

		Return d
	End Function

	Public ReadOnly Property MD5_() As String
		Get
			Return CBinary.ToBase64(MD5, 10)
		End Get
	End Property

	Public Function MD5() As Guid
		Dim guids As New List(Of Guid)
		guids.Add(Me.Tables.MD5)
		guids.Add(Me.Views.MD5)
		guids.Add(Me.Procs.MD5)
		guids.Add(Me.ForeignKeys.MD5)
		guids.Add(Me.Indexes.MD5)
		guids.Add(Me.DefaultValues.MD5)
		guids.Add(Me.Migration.MD5)

		Return CBinary.MD5_(guids)
	End Function

	Private Shared Sub Diff(this As CViewInfoList, ref As CViewInfoList, ByRef missing As CViewInfoList, ByRef added As CViewInfoList, ByRef same As CViewInfoList, ByRef diff As CViewInfoList)
		For Each i As CViewInfo In this
			If ref.Index.ContainsKey(i.MD5) Then
				same.Add(i)
			ElseIf ref.Has(i.ViewName) Then
				diff.Add(i)
			Else
				added.Add(i)
			End If
		Next
		For Each i As CViewInfo In ref
			If Not this.Index.ContainsKey(i.MD5) Then
				If Not this.Has(i.ViewName) Then
					missing.Add(i)
				Else
					If Not diff.Has(i.ViewName) Then diff.Add(i)
				End If
			End If
		Next
	End Sub
	Private Shared Sub Diff(this As CPrimaryKeyList, ref As CPrimaryKeyList, ByRef missing As CPrimaryKeyList, ByRef added As CPrimaryKeyList, ByRef same As CPrimaryKeyList, ByRef diff As CPrimaryKeyList)
		For Each i As CPrimaryKey In this
			If ref.Index.ContainsKey(i.MD5) Then
				same.Add(i)
			ElseIf ref.Has(i.KeyName) Then
				diff.Add(i)
			Else
				added.Add(i)
			End If
		Next
		For Each i As CPrimaryKey In ref
			If Not this.Index.ContainsKey(i.MD5) Then
				If Not this.Has(i.KeyName) Then
					missing.Add(i)
				Else
					If Not diff.Has(i.KeyName) Then diff.Add(i)
				End If
			End If
		Next
	End Sub
	Private Shared Sub Diff(this As CTableInfoList, ref As CTableInfoList, ByRef missing As CTableInfoList, ByRef added As CTableInfoList, ByRef same As CTableInfoList, ByRef diff As CTableInfoList)
		For Each i As CTableInfo In this
			If ref.Index.ContainsKey(i.MD5) Then
				same.Add(i)
			ElseIf ref.Has(i.TableName) Then
				diff.Add(i)
			Else
				added.Add(i)
			End If
		Next
		For Each i As CTableInfo In ref
			If Not this.Index.ContainsKey(i.MD5) Then
				If Not this.Has(i.TableName) Then
					missing.Add(i)
				Else
					If Not diff.Has(i.TableName) Then diff.Add(i)
				End If
			End If
		Next
	End Sub
	Private Shared Sub Diff(this As CProcedureList, ref As CProcedureList, ByRef missing As CProcedureList, ByRef added As CProcedureList, ByRef same As CProcedureList, ByRef diff As CProcedureList)
		For Each i As CProcedure In this
			If ref.Index.ContainsKey(i.MD5) Then
				same.Add(i)
			ElseIf ref.Has(i.Name) Then
				diff.Add(i)
			Else
				added.Add(i)
			End If
		Next
		For Each i As CProcedure In ref
			If Not this.Index.ContainsKey(i.MD5) Then
				If Not this.Has(i.Name) Then
					missing.Add(i)
				Else
					If Not diff.Has(i.Name) Then diff.Add(i)
				End If
			End If
		Next
	End Sub
	Private Shared Sub Diff(this As CDefaultValueList, ref As CDefaultValueList, ByRef missing As CDefaultValueList, ByRef added As CDefaultValueList, ByRef same As CDefaultValueList, ByRef diff As CDefaultValueList)
		Dim d As New CDefaultValueDiff(this, ref)
		missing = d.Missing
		added = d.Extra
		same = d.Same
		diff = d.Diff

		For Each i As CDefaultValue In this
			If ref.Index.ContainsKey(i.MD5) Then
				same.Add(i)
			ElseIf ref.Has(i.Name) Then
				diff.Add(i)
			Else
				added.Add(i)
			End If
		Next
		For Each i As CDefaultValue In ref
			If Not this.Index.ContainsKey(i.MD5) Then
				If Not this.Has(i.Name) Then
					missing.Add(i)
				Else
					If Not diff.Has(i.Name) Then diff.Add(i)
				End If
			End If
		Next
	End Sub


	Public Function MigrationHistory_Lazy(db As CDataSrc) As CMigrationHistory
		If IsNothing(Me.MigrationHistory) OrElse Me.MigrationHistory.Count = 0 Then
			Me.MigrationHistory = New CMigrationHistory(db)
		End If
		Return Me.MigrationHistory
	End Function


	Public Function ForceMigrationHistory(refVersion As CSchemaInfo, db As CDataSrc, refDb As CDataSrc) As Boolean

		Dim history As CMigrationHistory = refDb.MigrationHistory()
		Dim changes As CMigrationHistory = history.GetChanges(Me.Migration)

		For Each i As CMigration In changes
			i.InsertInto(db)
		Next

		'Update cache
		Me.MigrationHistory = Nothing
		Me.Migration = Nothing
		Return True
	End Function

	Public Function ForceMigrationScript(ref As CSchemaInfo, db As CDataSrc, dbRef As CDataSrc) As String
		Dim sb As New StringBuilder()
		For Each cmd As CCommand In ForceMigrationCommands(ref, db, dbRef)
			If sb.Length = 0 Then sb.AppendLine(cmd.Text)
			sb.Append("{")

			For Each p As IDataParameter In cmd.ParametersNamed
				If TypeOf (p.Value) Is Byte() Then
					sb.Append(CUtilities.CountSummary(CType(p.Value, Byte()).Length, "byte")).Append(",")
				ElseIf TypeOf (p.Value) Is Integer Then
					sb.Append(CUtilities.CountSummary(CInt(p.Value), "byte")).Append(",")
				Else
					sb.Append(p.Value).Append(",")
				End If
			Next
			sb.Append("}")
		Next
		Return sb.ToString()
	End Function

	Public Function ForceMigrationCommands(ref As CSchemaInfo, db As CDataSrc, refDb As CDataSrc) As List(Of CCommand)
		Dim refHistory As CMigrationHistory = ref.MigrationHistory_Lazy(refDb)
		Dim changes As CMigrationHistory = refHistory.GetChanges(Me.Migration)

		Dim list As New List(Of CCommand)()
		For Each i As CMigration In changes
			list.Add(i.InsertCmd_(db))
		Next
		Return list

	End Function




	Public Overrides Function ToString() As String
		Dim sb As New Text.StringBuilder
		If Me.Tables.Count > 0 Then
			sb.Append(CUtilities.NameAndCount("Table", Me.Tables.Names))
		End If
		If Me.Views.Count > 0 Then
			sb.Append(CUtilities.NameAndCount("View", Me.Views.Names))
		End If
		If Me.Tables.AllColumns.Count > 0 Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append(CUtilities.NameAndCount("Columns", Me.Tables.AllColumns.Count + Me.Views.AllColumns.Count))
		End If
		If Me.Procs.Count > 0 Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append(CUtilities.NameAndCount("Proc", Me.Procs.StoredProcs))
		End If
		If Me.Procs.Functions.Count > 0 Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append(CUtilities.NameAndCount("Fn", Me.Procs.Functions))
		End If
		If Me.ForeignKeys.Count > 0 Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append(CUtilities.NameAndCount("FK", Me.ForeignKeys))
		End If
		If Me.PrimaryKeys.Count > 0 Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append(CUtilities.NameAndCount("PK", Me.PrimaryKeys))
		End If
		If Me.Indexes.Normal.Count > 0 Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append(CUtilities.NameAndCount("Index", Me.Indexes.Normal))
		End If
		If Me.Indexes.Unique.Count > 0 Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append(CUtilities.NameAndCount("Unique", Me.Indexes.Unique))
		End If
		If Not IsNothing(Me.Migration) Then
			If sb.Length > 0 Then sb.Append(", ")
			sb.Append("Migration: ").Append(Me.Migration.MigrationId)
		End If
		Return sb.ToString
	End Function
End Class